💥 Standalone Activities for Ruby#443
Conversation
…ds, and new nullability for old fields, and a factory for creating the standalone version. Enums for activity id reuse and conflict policies. SAA-specific errors.
…e polling. Add test_async_completion_heartbeat_and_fail_standalone in addition to previous heartbeat test. Don’t use queue mechanism for gathering id info when it can be gotten directly.
Consistency of parameter names and ordering. Add static_details.
…he result exception
Test 2+ activit retries.
Use constant for “activity:” prefix.
There was a problem hiding this comment.
Pull request overview
Adds first-class support for standalone activities (activities started directly from a Temporalio::Client, not from within a workflow), including lifecycle operations (start/execute/result/describe/cancel/terminate), visibility querying, interceptor hooks, and updated activity context metadata to distinguish workflow-scheduled vs standalone execution.
Changes:
- Introduces
Client#start_activity/#execute_activity,Client::ActivityHandle, visibility APIs (list_activities/count_activities), and supporting data types/enums/errors. - Extends async-activity completion to accept standalone-form
ActivityIDReference, and updatesActivity::Infoto surface standalone-vs-workflow metadata (with workflow fields becoming nullable). - Bumps embedded core (Rust crates) and refreshes generated API/proto bindings; adds extensive integration tests and README documentation.
Reviewed changes
Copilot reviewed 57 out of 62 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| temporalio/test/worker_workflow_test.rb | Adjusts test to account for nullable workflow_id in Activity::Info. |
| temporalio/test/worker_activity_test.rb | Adjusts tests to account for nullable workflow_id in Activity::Info. |
| temporalio/test/test.rb | Enables dev-server dynamic config flags needed for standalone activities in tests. |
| temporalio/test/sig/client_workflow_interceptor_chain_test.rbs | RBS for new workflow interceptor chain ordering tests. |
| temporalio/test/sig/client_activity_test.rbs | RBS for standalone activity client tests. |
| temporalio/test/sig/client_activity_interceptor_chain_test.rbs | RBS for standalone activity interceptor chain ordering tests. |
| temporalio/test/sig/client_activity_hints_test.rbs | RBS for standalone activity hint propagation tests. |
| temporalio/test/sig/client_activity_async_completion_test.rbs | RBS for standalone activity async-completion tests. |
| temporalio/test/sig/activity_info_standalone_test.rbs | RBS for Activity::Info standalone-vs-workflow tests. |
| temporalio/test/client/activity_id_reference_test.rb | Tests new standalone form of ActivityIDReference. |
| temporalio/test/client_workflow_interceptor_chain_test.rb | Verifies workflow interceptor ordering convention. |
| temporalio/test/client_activity_test.rb | End-to-end tests for standalone activity lifecycle + visibility + policies. |
| temporalio/test/client_activity_interceptor_chain_test.rb | Verifies standalone activity interceptor coverage and ordering. |
| temporalio/test/client_activity_hints_test.rb | Verifies arg/result hint propagation across standalone + async-completion paths. |
| temporalio/test/client_activity_async_completion_test.rb | Tests async completion (complete/heartbeat/fail/cancel) for standalone activities. |
| temporalio/test/activity_info_standalone_test.rb | Validates Activity::Info fields inside activity body for standalone vs workflow execution. |
| temporalio/sig/temporalio/internal/client/implementation.rbs | Adds typing for new internal constant used by standalone async completion routing. |
| temporalio/sig/temporalio/error/failure.rbs | Adds RBS for ActivityAlreadyStartedError. |
| temporalio/sig/temporalio/error.rbs | Adds RBS for ActivityFailedError. |
| temporalio/sig/temporalio/common_enums.rbs | Adds RBS for ActivityIDReusePolicy and ActivityIDConflictPolicy. |
| temporalio/sig/temporalio/client/interceptor.rbs | Adds RBS for new activity-related interceptor inputs and outbound hooks. |
| temporalio/sig/temporalio/client/connection/cloud_service.rbs | Adds RBS for new CloudService custom role RPCs. |
| temporalio/sig/temporalio/client/activity_id_reference.rbs | Updates RBS for standalone-form fields and constructors. |
| temporalio/sig/temporalio/client/activity_handle.rbs | Adds RBS for new Client::ActivityHandle. |
| temporalio/sig/temporalio/client/activity_execution.rbs | Adds RBS for ActivityExecution and rich Description. |
| temporalio/sig/temporalio/client/activity_execution_status.rbs | Adds RBS for standalone activity execution status enum wrapper. |
| temporalio/sig/temporalio/client/activity_execution_count.rbs | Adds RBS for activity count + group-by result type. |
| temporalio/sig/temporalio/client.rbs | Adds RBS for new public standalone activity APIs. |
| temporalio/sig/temporalio/api/payload_visitor.rbs | Updates visitor signatures for new CloudService responses. |
| temporalio/sig/temporalio/api/cloud/namespace/v1/message.rbs | Updates generated Cloud namespace message typings. |
| temporalio/sig/temporalio/api/cloud/identity/v1/message.rbs | Updates generated Cloud identity message typings (custom roles). |
| temporalio/sig/temporalio/api/cloud/cloudservice/v1/request_response.rbs | Updates generated CloudService request/response typings. |
| temporalio/sig/temporalio/activity/info.rbs | Updates Activity::Info typing (new fields + workflow fields now nullable). |
| temporalio/sig/temporalio/activity/definition.rbs | Adds RBS for _type_and_hints_from_parameter helper used by client APIs. |
| temporalio/lib/temporalio/testing/activity_environment.rb | Updates test activity environment default Activity::Info construction for new required fields. |
| temporalio/lib/temporalio/internal/worker/activity_worker.rb | Populates new Activity::Info fields (standalone-aware) when executing activities. |
| temporalio/lib/temporalio/internal/client/implementation.rb | Implements standalone activity RPCs + visibility + long-poll outcome fetch + async completion routing changes. |
| temporalio/lib/temporalio/error/failure.rb | Adds Error::ActivityAlreadyStartedError. |
| temporalio/lib/temporalio/error.rb | Adds Error::ActivityFailedError. |
| temporalio/lib/temporalio/common_enums.rb | Adds standalone activity ID reuse/conflict policy enums. |
| temporalio/lib/temporalio/client/interceptor.rb | Adds activity interceptor inputs and outbound methods. |
| temporalio/lib/temporalio/client/connection/cloud_service.rb | Adds CloudService custom role RPC wrappers. |
| temporalio/lib/temporalio/client/activity_id_reference.rb | Extends ActivityIDReference with standalone form + predicate. |
| temporalio/lib/temporalio/client/activity_handle.rb | Adds Client::ActivityHandle (result/describe/cancel/terminate). |
| temporalio/lib/temporalio/client/activity_execution.rb | Adds ActivityExecution + Description wrappers for list/describe results. |
| temporalio/lib/temporalio/client/activity_execution_status.rb | Adds standalone activity execution status constants. |
| temporalio/lib/temporalio/client/activity_execution_count.rb | Adds count result type with aggregation groups. |
| temporalio/lib/temporalio/client.rb | Adds activity_handle, start_activity, execute_activity, list_activities, count_activities public APIs. |
| temporalio/lib/temporalio/api/payload_visitor.rb | Updates payload visitor for new CloudService custom role responses. |
| temporalio/lib/temporalio/api/cloud/namespace/v1/message.rb | Updates generated Cloud namespace protobuf Ruby bindings. |
| temporalio/lib/temporalio/api/cloud/identity/v1/message.rb | Updates generated Cloud identity protobuf Ruby bindings. |
| temporalio/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb | Updates generated CloudService protobuf Ruby bindings. |
| temporalio/ext/src/client_rpc_generated.rs | Adds dispatch entries for CloudService custom role RPCs. |
| temporalio/ext/Cargo.toml | Bumps embedded Rust crates to 0.4.0. |
| temporalio/Cargo.lock | Lockfile updates for Rust dependency bump. |
| README.md | Documents standalone activity usage with runnable examples. |
| CHANGELOG.md | Adds changelog entry for standalone activities. |
| .gitattributes | Adds union merge driver config for CHANGELOG.md. |
Files not reviewed (4)
- temporalio/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb: Language not supported
- temporalio/lib/temporalio/api/cloud/cloudservice/v1/service.rb: Language not supported
- temporalio/lib/temporalio/api/cloud/identity/v1/message.rb: Language not supported
- temporalio/lib/temporalio/api/cloud/namespace/v1/message.rb: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| handle = client.activity_handle('my-activity-id') | ||
| description = handle.describe # ActivityExecution::Description | ||
| result = handle.result # blocks until the activity reaches a terminal state | ||
| handle.cancel('reason for cancel'). # or |
chris-olszewski
left a comment
There was a problem hiding this comment.
Looks very good! Mostly minor things and a few areas that I think could use more tests.
| ``` | ||
|
|
||
| Standalone activities require the dev server to enable the SAA feature flags. See the test bootstrap in | ||
| `temporalio/test/test.rb` for the required `--dynamic-config-value` flags. |
There was a problem hiding this comment.
Let us just be explicit here instead of referencing a file that might become stale.
| `temporalio/test/test.rb` for the required `--dynamic-config-value` flags. | |
| Standalone activities require the dev server to enable the following feature flags: `frontend.activityAPIsEnabled`, `activity.enableStandalone`, `history.enableChasm`, `history.enableTransitionHistory`. |
There was a problem hiding this comment.
These flags are not needed anymore (as of CLI 1.7.0).
| def test_execute_activity_by_name | ||
| with_activity_worker([SimpleActivity]) do |task_queue| | ||
| result = env.client.execute_activity( | ||
| 'SimpleActivity', |
There was a problem hiding this comment.
AI pointed out that we don't have test coverage for starting activities by symbol, activity instance, or activity definition. Would be nice to have some verification there, at bare minimum we should verify the unhappy path here e.g. dynamic activity or a true junk input.
| super | ||
| end | ||
|
|
||
| def cancel_activity(input) |
There was a problem hiding this comment.
This interceptor isn't covered by any test
| end | ||
| end | ||
|
|
||
| def test_list_activities_simple_list_is_accurate |
There was a problem hiding this comment.
We should probably have some test coverage of the pagination logic just as a smoke test.
maciejdudko
left a comment
There was a problem hiding this comment.
Great work! I left a bunch of comments, most of them are about docstrings or about nullability of some fields, but there's a couple of functional ones. The biggest is lack of input validation in start_activity.
| # @return [String] Namespace this activity is on. | ||
| # @deprecated Use {#namespace} instead. The value is identical regardless of whether the activity is | ||
| # standalone or scheduled from a workflow. |
There was a problem hiding this comment.
Like other SDKs, workflow_namespace should be nil for Standalone Activities.
| # @!attribute namespace | ||
| # @return [String] Namespace this activity is on. Always set, including for standalone activities. Prefer this | ||
| # accessor over the deprecated {workflow_namespace}. |
There was a problem hiding this comment.
| # @!attribute namespace | |
| # @return [String] Namespace this activity is on. Always set, including for standalone activities. Prefer this | |
| # accessor over the deprecated {workflow_namespace}. | |
| # @!attribute namespace | |
| # @return [String] Namespace this activity is on. |
We don't need to reference the other, deprecated attribute.
| attr_reader task_token: String | ||
| attr_reader workflow_id: String | ||
| attr_reader workflow_id: String? | ||
| attr_reader workflow_namespace: String |
There was a problem hiding this comment.
| attr_reader workflow_namespace: String | |
| attr_reader workflow_namespace: String? |
| ``` | ||
|
|
||
| Standalone activities require the dev server to enable the SAA feature flags. See the test bootstrap in | ||
| `temporalio/test/test.rb` for the required `--dynamic-config-value` flags. |
There was a problem hiding this comment.
These flags are not needed anymore (as of CLI 1.7.0).
| - 💥 Standalone Activities: activities that execute independently of any workflow. | ||
| ``` | ||
| handle = client.start_activity( | ||
| MyActivity, | ||
| 'some-arg', | ||
| id: 'my-activity-id', | ||
| task_queue: 'my-task-queue', | ||
| start_to_close_timeout: 60 | ||
| ) | ||
| result = handle.result # blocks until the activity completes | ||
| ``` No newline at end of file |
There was a problem hiding this comment.
Usually we don't do code snippets in release notes. There should be 1-2 paragraphs of feature description and a link to documentation. Breaking changes should be described in a separate section. See release notes in other SDKs.
| # Enable activity pause | ||
| '--dynamic-config-value', 'frontend.activityAPIsEnabled=true' | ||
| # Activity pause + Standalone Activities | ||
| '--dynamic-config-value', 'frontend.activityAPIsEnabled=true', | ||
| '--dynamic-config-value', 'activity.enableStandalone=true', | ||
| '--dynamic-config-value', 'history.enableChasm=true', | ||
| '--dynamic-config-value', 'history.enableTransitionHistory=true' |
There was a problem hiding this comment.
These flags are not needed anymore (as of CLI 1.7.0).
| nil | ||
| end | ||
|
|
||
| def start_activity(input) |
There was a problem hiding this comment.
This method is missing input validation.
- activity_id not empty
- task_queue not empty
- either schedule_to_close_timeout or start_to_close_timeout is set
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Add standalone activity API (
Client#start_activity+ friends)Introduces support for standalone activities — activities that execute independently of any workflow.
💥 Breaking Changes:
Activity::Info#workflow_id,#workflow_run_id,#workflow_typeare now nullable (String?); they returnnilwhen the activity is standalone.Activity::Info#workflow_namespaceis now marked@deprecatedin favor of the new#namespaceaccessor. Both fields always carry the same value regardless of standalone-vs-workflow context; the rename matches the cross-SDK convention.New public API surface:
Client#start_activity/Client#execute_activity— start a standalone activity execution by name, class, instance, orActivity::Definition::Info;execute_activityisstart_activity+handle.resultas a shortcutClient#activity_handle(activity_id, activity_run_id:, result_hint:)— get a handle to an existing standalone activity (e.g. one started by a different process)Client#list_activities(query)/Client#count_activities(query)— visibility queries over standalone activities;list_activitiesreturns anEnumerator<ActivityExecution>Client::ActivityHandle— handle returned bystart_activity/activity_handle; provides#result(result_hint:, rpc_options:),#describe,#cancel(reason),#terminate(reason)Client::ActivityExecution— lightweight metadata used inlist_activitiesresultsClient::ActivityExecution::Description— rich descriptor returned byActivityHandle#describe; subclass ofActivityExecution. Exposes ~25 fields from the proto (status,attempt,retry_policy,last_failure,last_heartbeat_time,priority,canceled_reason,static_summary/static_details, etc.)Client::ActivityExecutionCount+::AggregationGroup— result ofcount_activities(), with group-by supportClient::ActivityExecutionStatus— enum module wrapping the proto's status valuesActivityIDReusePolicy+ActivityIDConflictPolicy— enum modules inlib/temporalio/common_enums.rbmirroring the workflow-side policiesError::ActivityAlreadyStartedError(inerror/failure.rb) — raised bystart_activitywhen the server rejects a duplicate IDError::ActivityFailedError(inerror.rb) — raised byhandle.resultwhen the activity terminates in failure;causecarries the underlying failure (ApplicationError,CanceledError,TimeoutError,TerminatedError)New interceptor API:
Client::Interceptor::StartActivityInput,DescribeActivityInput,CancelActivityInput,TerminateActivityInput,ListActivitiesInput,CountActivitiesInput,FetchActivityOutcomeInput— seven new Data.define input typesClient::Interceptor::Outboundextended with seven matching pass-through methods (start_activity,describe_activity,cancel_activity,terminate_activity,list_activities,count_activities,fetch_activity_outcome)Activity::Infoadditions:#activity_run_id— run-scoped id assigned by the server to each standalone activity execution (nilfor workflow-dispatched)#namespace— canonical namespace accessor (preferred over the deprecated#workflow_namespace)#in_workflow?— predicate distinguishing workflow-dispatched activities from standalone onesClient#async_activity_handleextension (no new method):ActivityIDReferenceconstructed viaActivityIDReference.for_standalone(activity_id:, activity_run_id:).ActivityIDReferenceadditions:ActivityIDReference.for_standalone(activity_id:, activity_run_id: nil)— class factory for the new standalone shape.#standalone?— predicate for branching in async-completion routingCI / dev server:
frontend.activityAPIsEnabled,activity.enableStandalone,history.enableChasm,history.enableTransitionHistory(added totest/test.rb).sdk-coresubmodule toa22517e4(Rust crate 0.4.0) to pick up the worker-sidecoresdk.activity_task.Start.run_idproto field used to plumbactivity_run_idintoActivity::Info.Tests:
test/client_activity_test.rb(28 tests) — integration tests covering the full lifecycle: start, execute, by-name, already-started, timeouts, failure-throws-ActivityFailedError, describe (running / terminated / canceled / retried / raw-info parity), state-transition-count, terminate-result-throws, polling-correctness (with timing assertions across one server long-poll deadline boundary), list, count, get-handle-with-nil-run-id, cancel-transitions-to-CANCELED, retry-policy / priority / id-reuse / id-conflict-policy round tripstest/client_activity_async_completion_test.rb(6 tests) — async completion viaAsyncActivityHandleagainst a standaloneActivityIDReference: complete by activity_id, complete with run_id, heartbeat, fail, heartbeat+fail, report_cancellationtest/client_activity_hints_test.rb(6 tests) — definition hints used for client-side arg encode and worker-side decode, definitionresult_hintused for worker-side encode and client-side decode, call-site overrides onstart_activity, by-name activities produce nil hints,handle.result(result_hint:)overrides,activity_handle(id, result_hint:)constructor-time hint,AsyncActivityHandle#complete(result, result_hint:)propagates the hinttest/client_activity_interceptor_chain_test.rb(5 tests) — multi-interceptor ordering for activity client calls; verifies first-added-is-outermosttest/client_workflow_interceptor_chain_test.rb(2 tests) — mirror of the above for the workflow client chaintest/activity_info_standalone_test.rb(2 tests) —Activity::Infostandalone-vs-workflow fields asserted from inside the activity bodytest/client/activity_id_reference_test.rb(5 tests) — factory shapes,for_standalonecorrectness, both forms coexist independentlyDocumentation:
README.md— added "Standalone Activities" subsection under "Activities" with five runnable code examples (start, execute, get-handle + describe/result/cancel/terminate, list/count, in-activity-body context introspection)